//////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2011, Xi Minggao <brooks.xi@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice, this
//     list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
// WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//////////////////////////////////////////////////////////////////////////////////////////

#include "net/EPoll.hxx"
#include "net/Event.hxx"
#include "comm/Exception.hxx"
#include <errno.h>
#include <string.h>
#include <boost/assert.hpp>

using namespace kiwii::net;
using namespace kiwii::comm;
using namespace boost;
using namespace std;

EPoll::EPoll() : nfds_(0), bufsize_(0) {
  epollfd_ = epoll_create(128); // Since Linux 2.6.8, the argument of epoll_create() is unused.
				// So here is just an insignificant number.
  if (epollfd_ < 0) {
    throw Exception("epoll_create failed: ") << errinfo_errno(errno);
  }
}

EPoll::~EPoll() {
  close(epollfd_);
}

void EPoll::addEvent(int fd, unsigned int newEvents, unsigned int oldEvents) {
  if (newEvents == EV_NONE || newEvents == oldEvents) {
    return;
  }

  struct epoll_event ev;
  memset(&ev, 0, sizeof(ev));
  newEvents |= oldEvents; //merge old events
  if (newEvents & EV_READABLE) ev.events |= EPOLLIN;
  if (newEvents & EV_WRITABLE) ev.events |= EPOLLOUT;
  ev.data.fd = fd;

  int op = (oldEvents == EV_NONE) ? EPOLL_CTL_ADD : EPOLL_CTL_MOD;

  if (epoll_ctl(epollfd_, op, fd, &ev) < 0) {
    throw Exception("epoll_ctl() failed: ") << errinfo_errno(errno);
  }

  if (op == EPOLL_CTL_ADD) {
    nfds_++;
  }
}

void EPoll::delEvent(int fd, unsigned int ev2Del, unsigned int oldEvents) {
  unsigned int mask = oldEvents & (~ev2Del);
  if (mask == oldEvents) {
    return;
  }

  struct epoll_event ev;
  memset(&ev, 0, sizeof(ev));
  if (mask & EV_READABLE) ev.events |= EPOLLIN;
  if (mask & EV_WRITABLE) ev.events |= EPOLLOUT;
  ev.data.fd = fd;

  int op = (mask == EV_NONE) ? EPOLL_CTL_DEL : EPOLL_CTL_MOD;
  if (epoll_ctl(epollfd_, op, fd, &ev) < 0) {
    throw Exception("epoll_ctl() failed: ") << errinfo_errno(errno);
  }

  if (op == EPOLL_CTL_DEL) {
    nfds_--;
  }
}

void EPoll::poll(struct timeval const* timeout, vector<FiredIOEvent>& fired) {
  BOOST_ASSERT(nfds_ > 0);

  if (bufsize_ < nfds_) {
    events_.reset(new struct epoll_event[nfds_ << 1]);
    bufsize_ = (nfds_ << 1); // expand array events_ to 2 time nfds_ large.
			     // we need assignment here instead before the above statement for exception-safe code.
  } else if (bufsize_ / 2 > nfds_) {
    events_.reset(new struct epoll_event[nfds_ >> 1]);
    bufsize_ = (nfds_ >> 1); // shrink events_ to half size
  }

  int n;
  if ((n = epoll_wait(epollfd_, events_.get(), nfds_, 
		      timeout ? (timeout->tv_sec * 1000 + timeout->tv_usec / 1000) : -1)) < 0) {
    throw Exception("epoll_wait() failed: ") << errinfo_errno(errno);
  }

  fired.clear();
  for (int i = 0; i < n; i++) {
    unsigned int mask = 0;
    if (events_[i].events & EPOLLIN) mask |= EV_READABLE;
    if (events_[i].events & EPOLLOUT) mask |= EV_WRITABLE;

    fired.push_back(FiredIOEvent(events_[i].data.fd, mask));
  }
}
